如何做出一個好的 NodeJS 模組?


Posted by TechBridge 技術週刊 on 2018-02-14

前言

這篇文章需要知道什麼是 NodeJS,簡單來說 NodeJS 讓 Javascript 可以變成一個後端的語言,而不是僅限瀏覽器才能解讀的前端語言。如果你還沒用過 NodeJS,首先需要安裝他,Windows 和 MacOS 的用戶可以直接上官網下載,Linux 的用戶推薦使用 NVM 安裝。

回想我們一開始學 C++ 的時候,如果想要用數學的函式,必須引入 cmath 函式庫(Library)才能呼叫 sincos 等函式。而我們在寫 NodeJS 的時候,一樣可能會需要引入函式庫加入主程式,在 NodeJS 中我們稱為模組(Module)。寫一個模組雖然很簡單,但若是想要讓自己寫的模組給更多人使用,就要注意一些小細節,讓我娓娓道來。

簡單的 NodeJS 模組

在 NodeJS 中要寫模組很簡單,假設我今天寫了一個模組叫做 greeting,我可以這樣寫:

  1. 建立一個檔案叫做 greeting.js
  2. 裡面內容是:
     // greeting.js
     function greeting() {
         console.log("哈囉!你好嗎~");
     }
     module.export = greeting;
    
  3. 注意到 module.export 是要輸出成模組的意思。

假設同目錄下,有一個主程式 index.js,當我想使用 greeting 的時候,我就可以引用模組來使用,呼叫方式是 require() 如以下:

// index.js
const greeting = require('./greeting'); // 要注意路徑
greeting();

這時候打開終端機,執行主程式:

$ node index.js

就會輸出

> 哈囉!你好嗎~

這就是最簡單的 NodeJS 模組了!

NPM 套件管理

介紹 NPM

今天假設我需要一個模組負責影像處理,我可能會需要自己寫一個模組,裡面可能包含裁切、變色、縮放等功能。但是俗話說的好:

Don't reinventing the wheel!

不要自己造輪子,買一個回來不就得了!寫軟體也一樣,你遇到的問題,別人八成也遇過。意思是別人可能已經有寫好套件了!

而 NodeJS 世界中,有一個平台專門讓大家把自己寫的套件分享的平台,叫做 NPM,任何人都能下載裡面的套件,或是把套件上傳到平台上。在繼續之前,我們先來看一篇新聞,以下我擷取新聞稍做修改:

有一個稱為 left-pad 的模組,只有11行程式碼︰

    module.exports = leftpad;
    function leftpad(str, len, ch) {
        str = String(str);
        var i = -1;
        if (!ch && ch !== 0) ch = ' ';
        len = len - str.length;
        while (++i < len) {
            str = ch + str;
        }
        return str;
    }

這個模組的工作非常簡單︰把一個字串的開頭補上字符,使其長度符合要求。假如程式員希望所有字串都是 5 個字元,不夠長的話都用 0 補上,使用 left-pad 就能把「369」變成「00369」。

如此簡單的 left-pad 很受歡迎——根據 NPM 統計數據,在過去一個月有超過 200萬次下載。很多開發人員也許未曾聽 left-pad,但在不經意的情況下用到這個模組——可能是他們使用的模組用到 left-pad,可能是他們使用的模組所使用的模組……如此類推。

先不管新聞討論爭議的問題(軟體自由與商業利益),我們看到一個簡單的套件對世界就有如此大的影響力,套件不一定要很厲害,但是好用最重要,打個比方就像螺絲,牽一髮而動全身。

分享到 NPM

假設我們今天想分享一個套件,以我開發的小套件date2obj為例,告訴大家怎樣分享到 NPM 上。簡單介紹一下 date2obj,在寫 Javascipt 的時候,Date 時間物件常常要做分離的動作,雖然程式碼很短,但每個專案都要 copy 一次真的很麻煩,所以我弄成可以呼叫的套件。

分享到 NPM 的操作步驟如下:

  1. 在 Github 建立 date2obj 專案。
  2. 接著直接 git clone 下來。
  3. 終端機 cd date2obj 進入資料夾。
  4. 終端機輸入 npm init 用來初始化專案,如果有裝好 NodeJS 的話,npm 就是安裝好的了。
  5. 接下來終端機會出現問題,要你填寫專案名稱、版本號、作者、關鍵字等等資訊。什麼都不輸入,按下 enter 就是省略的意思。其中他會問主程式就照預設的 index.js 即可,而 test 可以先略過,版本號其實有它的意義,不過這邊我們可以隨便輸入或照預設的1.0.0。完成之後應該會看到目錄底下多一個 package.json 的檔案。
  6. 這時候目錄裡面應該只有一個檔案:package.json,我們要手動建立一個 index.js 檔案。
  7. 假設我們在 index.js 打好程式了。這邊我們來觀察 date2objindex.js,最底下有一行 module.exports = date2obj;,這是關鍵!我們剛剛設定依照步驟將主程式設定為 index.js,在 package.json 裡面就是 "main": "index.js"

    NPM 會根據 main 得到這個專案的的輸出地方是 index.js,所以我們在 index.js 的最後一行說明我要輸出的函式是什麼,在這邊是 function date2obj()

  8. index.js 也可以引用別人的 NPM 套件,甚至專案目錄裡面的程式可以非常複雜,但最後要有一個輸出點,讓 NPM 知道以後別人要用這個套件要從哪邊進入。
  9. 到目前為止,專案裡面只有兩個檔案package.jsonindex.js。但這樣已經是合格的套件了喔!這兩個檔案缺一不可,package.json 可以想像成貨品的標籤,index.js 則是貨物本身。
  10. 接下來就可以分享到 NPM 平台上囉!終端機輸入 npm publish 來上傳你的模組,第一次使用這個指令他會需要建立個人資訊和密碼,照著提示做即可。
  11. 之後假設你有更新模組,package.json 的版本號要增加,例如 0.0.10.0.2,再輸入 npm publish 就會更新了!

看到這邊,你已經能自己寫一個模組,並分享到 NPM 和世界的人共享開源!不過一個好的套件,到這邊卻還沒完成!

還缺什麼?

其實目前為止已經合格了。不過我們可以讓他更好。以軟體工程師的角度,我會希望再增加幾點:

  • 說明文件(Document)
  • 單元測試(Unit Test)
  • 持續整合(CI)

說明文件

如果套件的目的是自己用,那麼沒有說明文件倒是無所謂,自己能記住就好。但是我們都已經發佈到 NPM 上了,代表其實我們希望別人也能使用,你不能期望別人翻開你的原始碼,研究半天才知道怎麼使用它,事實上通常我看到說明文件艱澀難懂、甚至沒有的時候,就直接找下一個可行的套件了。所以說明文件可以說是至關重要。

最簡單的說明文件就是在專案根目錄底下建立一個 README.md 檔案。README.md檔案被 NPM 和 Github 預設當作專案首頁,意思是別人點進去看到的第一眼,就是README.md的內容。通常裡面簡單描述這個模組可以幹嘛?該如何使用?比較大型的模組通常會把說明文件用網站呈現,並在 README.md 中引導你去網站查看文件。

不論自己用或給別人用,有說明文件都是比較方便的。

單元測試

為什麼要寫單元測試?Google 一下會有很多答案,但以我來說,大概可以歸納兩點:

  • 確保自己寫的程式沒有問題
  • 別人看了比較心安

寫程式非常容易出錯,最低級的錯誤才有機會在編譯、執行的時候馬上看出來,那種藏得非常深的問題,甚至有時候是超大漏洞,往往在寫的時候看不出來。單元測試有幫自己檢查的好處,可以盡可能列出可能的錯誤,在測試的時候即時發現,關於如何寫測試本篇不多敘述。

另外一點就是,當我想用別人套件的時候,我會看他有沒有寫測試,如果沒有的話,實在很難令人放心他沒問題,所以發佈的套件必寫測試幾乎已經成為不成文規定了!

持續整合

通常模組的專案還會再加上持續整合的服務,像是 TravisCircleCI 等等。簡單說明持續整合的概念,是每次程式碼有變動的時候,都要能確保它可以編譯、執行、通過測試,以免下次改程式不小心整個模組爛掉,這種檢查有專門的服務商在做,可以完整支援與 Github 同步,每當有新的 Pull Request 或是有新 commit 到專案的分支,持續整合就會做檢查,告訴你新的程式碼有沒有問題,假設都沒問題,就可以把新的程式碼 merge 進 master 主幹中。

最後最後

你寫了一個好的套件,總是希望人能用吧?不然不就白寫了嗎!!

有極小機率,你的套件莫名其妙爆紅,但千萬別指望這樣。比較務實的做法是,做一點行銷手段,到社群分享你的套件,寫部落格介紹你套件的原理,在大型專案中自告奮勇提出使用你的套件等等。

希望大家都能寫出好的 NodeJS 模組!


關於作者

劉安齊

軟體工程師,熱愛寫程式,更喜歡推廣程式讓更多人學會


#nodejs #npm #module #tutorial









Related Posts

[C#] Asp.net mvc 搭配 axios下載檔案

[C#] Asp.net mvc 搭配 axios下載檔案

redis 套件的 Property 'on' does not exist on type 'RedisClientType'

redis 套件的 Property 'on' does not exist on type 'RedisClientType'

Linux Command 命令列指令與基本操作入門教學

Linux Command 命令列指令與基本操作入門教學




Newsletter




Comments